Java2D: An Introduction and Tutorial
<-- Java Programming Resources home. Core Web Programming home. -->

This tutorial presents a quick introduction to the basics of Java2D and the use of the Graphics2D class in Java 1.2. It is not a tutorial on general Java programming, a tutorial on all of Java 1.2, or even a tutorial on programming with Swing or JFC. (However, many such tutorials are available. See the Java FAQs and Tutorials collection.)

JHU/APL Logo This tutorial © Marty Hall. Created for work in the Research and Technology Development Center of the Johns Hopkins University Applied Physics Lab, for courses in the Johns Hopkins Part-Time MS Program in Computer Science, and for various industry seminars and Java short courses.

CWP/2E Note: An updated and expanded version of this material appears in Core Web Programming 2nd Edition. All source code (plus sample chapters) is available at http://www.corewebprogramming.com/.


Table of Contents

1. Introduction
1.1 Overview
1.2 Main New Features
1.3 General Approach
2. Drawing Shapes
2.1 Overview
2.2 Shape Classes
2.3 Drawing Shapes: Example Code
2.4 Drawing Shapes: Example Output
3. Paint Styles
3.1 Overview
3.2 Paint Classes
3.3 Transparency
3.4 Gradient Fills: Example Code
3.5 Gradient Fills: Example Output
3.6 Tiled Images: Overview
3.7 Tiled Images: Example Code
3.8 Tiled Images: Example Output
4. Transparency
4.1 Overview
4.2 Transparency: Example Code
4.3 Transparency: Example Output
5. Using Local Fonts
5.1 Overview
5.2 Listing Available Fonts: Example Code
5.3 Drawing with Local Fonts: Example Code
5.4 Drawing with Local Fonts: Example Output
6. Stroke Styles
6.1 Overview
6.2 Stroke Attributes
6.3 Stroke Thickness: Example Code
6.4 Stroke Thickness: Example Output
6.5 Dashed Lines: Example Code
6.6 Dashed Lines: Example Output
6.7 Line Cap and Join Styles: Example Code
6.8 Line Cap and Join Styles: Example Output
7. Coordinate Transformations
7.1 Overview
7.2 Translations and Rotations: Example Code
7.3 Translations and Rotations: Example Output
7.4 Shear Transformations
7.5 Shear Transformations: Example Code
7.6 Shear Transformations: Example Output
8. Conclusions
8.1 Requesting More Accurate Drawing: Rendering Hints
8.2 Summary of Using Java2D
8.3 Other Java2D Capabilities
8.4 Learning More About Java2D


1. Introduction to Java2D

1.1 Overview

In Java 1.2, the paintComponent method is supplied with a Graphics2D object (a subclass of Graphics), which contains a much richer set of drawing operations. It includes pen widths, dashed lines, image and gradient color fill patterns, the use of arbitrary local fonts, a floating point coordinate system, and a number of coordinate transformation operations. However, to maintain compatibility with Swing as used in Java 1.1, the declared type of the paintComponent argument is Graphics, so you have to cast it to Graphics2D before using it.

Java 1.1   Java 1.2
public void paint(Graphics g) {
  // Set pen parameters
  g.setColor(someColor);
  g.setFont(someLimitedFont);
  // Draw a shape
  g.drawString(...);
  g.drawLine(...)
  g.drawRect(...);     // outline
  g.fillRect(...);     // solid
  g.drawPolygon(...);  // outline
  g.fillPolygon(...);  // solid
  g.drawOval(...);     // outline
  g.fillOval(...);     // solid
  ...
}
 
public void paintComponent(Graphics g) {
  // Clear off-screen bitmap
  super.paintComponent(g);
  // Cast Graphics to Graphics2D
  Graphics2D g2d = (Graphics2D)g;
  // Set pen parameters
  g2d.setPaint(fillColorOrPattern);
  g2d.setStroke(penThicknessOrPattern);
  g2d.setComposite(someAlphaComposite);
  g2d.setFont(anyFont);
  g2d.translate(...);
  g2d.rotate(...);
  g2d.scale(...);
  g2d.shear(...);
  g2d.setTransform(someAffineTransform);
  // Allocate a shape 
  SomeShape s = new SomeShape(...);
  // Draw shape
  g2d.draw(s);  // outline
  g2d.fill(s);  // solid
}

1.2 Main New Features

1.3 General Approach


2. Drawing Shapes in Java2D

2.1 Drawing Shapes: Overview

With the AWT, you generally drew a shape by calling the drawXxx or fillXxx method of the Graphics object. In Java2D, you generally create a Shape object, then call either the draw or fill method of the Graphics2D object, supplying the Shape object as an argument. For example:
public void paintComponent(Graphics g) {
  super.paintComponent(g);
  Graphics2D g2d = (Graphics2D)g;
  // Assume x, y, and diameter are instance variables
  Ellipse2D.Double circle =
    new Ellipse2D.double(x, y, diameter, diameter);
  g2d.fill(circle);
  ...
}
You can still call the drawXxx methods if you like, however. This is necessary for drawString and drawImage, and possibly convenient for draw3DRect. Several classes have similar versions that store coordinates as either double precision numbers (Xxx.Double) or single precision numbers (Xxx.Float). The idea is that single precision coordinates might be slightly faster to manipulate on some platforms.

2.2 Shape Classes

Arguments to the Graphics2D draw and fill methods must implement the Shape interface. You can create your own shapes, of course, but following are the major built-in ones. Except for Rectangle and Polygon, which are Java 1.1 holdovers, these appear in the java.awt.geom package.

2.3 Drawing Shapes: Example Code

Download the source: ShapeExample.java, WindowUtilities.java, and ExitListener.java.

ShapeExample.java

import javax.swing.*; // For JPanel, etc.
import java.awt.*;           // For Graphics, etc.
import java.awt.geom.*;      // For Ellipse2D, etc.

/** An example of drawing/filling shapes with Java2D in Java 1.2.
 *
 *  From tutorial on learning Java2D at
 *  http://www.apl.jhu.edu/~hall/java/Java2D-Tutorial.html
 *
 *  1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class ShapeExample extends JPanel {
  private Ellipse2D.Double circle =
    new Ellipse2D.Double(10, 10, 350, 350);
  private Rectangle2D.Double square =
    new Rectangle2D.Double(10, 10, 350, 350);

  public void paintComponent(Graphics g) {
    clear(g);
    Graphics2D g2d = (Graphics2D)g;
    g2d.fill(circle);
    g2d.draw(square);
  }

  // super.paintComponent clears offscreen pixmap,
  // since we're using double buffering by default.

  protected void clear(Graphics g) {
    super.paintComponent(g);
  }

  protected Ellipse2D.Double getCircle() {
    return(circle);
  }

  public static void main(String[] args) {
    WindowUtilities.openInJFrame(new ShapeExample(), 380, 400);
  }
}

WindowUtilities.java

import javax.swing.*;
import java.awt.*;

/** A few utilities that simplify testing of windows in Swing.
 *  1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class WindowUtilities {

  /** Tell system to use native look and feel, as in previous
   *  releases. Metal (Java) LAF is the default otherwise.
   */

  public static void setNativeLookAndFeel() {
    try {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    } catch(Exception e) {
      System.out.println("Error setting native LAF: " + e);
    }
  }

  /** A simplified way to see a JPanel or other Container.
   *  Pops up a JFrame with specified Container as the content pane.
   */

  public static JFrame openInJFrame(Container content,
                                    int width,
                                    int height,
                                    String title,
                                    Color bgColor) {
    JFrame frame = new JFrame(title);
    frame.setBackground(bgColor);
    content.setBackground(bgColor);
    frame.setSize(width, height);
    frame.setContentPane(content);
    frame.addWindowListener(new ExitListener());
    frame.setVisible(true);
    return(frame);
  }

  /** Uses Color.white as the background color. */

  public static JFrame openInJFrame(Container content,
                                    int width,
                                    int height,
                                    String title) {
    return(openInJFrame(content, width, height, title, Color.white));
  }

  /** Uses Color.white as the background color, and the
   *  name of the Container's class as the JFrame title.
   */

  public static JFrame openInJFrame(Container content,
                                    int width,
                                    int height) {
    return(openInJFrame(content, width, height,
                        content.getClass().getName(),
                        Color.white));
  }
}

ExitListener.java

import java.awt.*;
import java.awt.event.*;

/** A listener that you attach to the top-level Frame or JFrame of
 *  your application, so quitting the frame exits the application.
 *  1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class ExitListener extends WindowAdapter {
  public void windowClosing(WindowEvent event) {
    System.exit(0);
  }
}

2.4 Drawing Shapes: Example Output

Tutorial output: drawing shapes in Java2D


3. Paint Styles in Java2D

3.1 Paint Styles: Overview

When you fill a Shape, the current Paint attribute of the Graphics2D object is used. This can be a Color (solid color), a GradientPaint (gradient fill gradually combining two colors), a TexturePaint (tiled image), or a new version of Paint that you write yourself. Use setPaint and getPaint to change and retrieve the Paint settings. Note that setPaint and getPaint supersede the setColor and getColor methods that were used in Graphics.

3.2 Paint Classes

Arguments to the Graphics2D setPaint method (and return values of getPaint) must implement the Paint interface. Here are the major built-in Paint classes:

3.3 Transparency

Transparency is not set in the Paint object, rather separately via an AlphaComposite object that is applied via setComposite. See Section 4 for details.

3.4 Gradient Fills: Example Code

Download the source: GradientPaintExample.java (plus ShapeExample.java, WindowUtilities.java, and ExitListener.java if you don't have them from the previous examples).
import java.awt.*;

/** An example of gradient fills with Java2D in Java 1.2.
 *
 *  From tutorial on learning Java2D at
 *  http://www.apl.jhu.edu/~hall/java/Java2D-Tutorial.html
 *
 *  1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class GradientPaintExample extends ShapeExample {
  // Red at (0,0), yellow at (175,175), changes gradually between.
  private GradientPaint gradient =
    new GradientPaint(0, 0, Color.red, 175, 175, Color.yellow,
                      true); // true means to repeat pattern

  public void paintComponent(Graphics g) {
    clear(g);
    Graphics2D g2d = (Graphics2D)g;
    drawGradientCircle(g2d);
  }

  protected void drawGradientCircle(Graphics2D g2d) {
    g2d.setPaint(gradient);
    g2d.fill(getCircle());
    g2d.setPaint(Color.black);
    g2d.draw(getCircle());
  }

  public static void main(String[] args) {
    WindowUtilities.openInJFrame(new GradientPaintExample(),
                                 380, 400);
  }
}

3.5 Gradient Fills: Example Output

Tutorial output: drawing shapes in Java2D

3.6 Tiled Images as Fill Patterns -- Overview

To use tiled images, you create a TexturePaint object and specify its use via the setPaint method of Graphics2D, just as with solid colors and gradient fills. The TexturePaint constructor takes a BufferedImage and a Rectangle2D as arguments. The BufferedImage specifies what to draw, and the Rectangle2D specifies where the tiling starts. Creating a BufferedImage to hold custom drawing is relatively straightforward: call the BufferedImage constructor with a width, a height, and a type of BufferedImage.TYPE_INT_RGB, then call createGraphics on that to get a Graphics2D with which to draw. It is a bit harder to create one from an image file. First load an Image from an image file, then use MediaTracker to be sure it is done loading, then create an empty BufferedImage using the Image width and height, then get the Graphics2D via createGraphics, then draw the Image onto the BufferedImage. This process has been wrapped up in the getBufferedImage method of my ImageUtilities class.

Note, however, that as of JDK1.2beta4, tiled images fail when used in conjunction with rotation transformations.

3.7 Tiled Images as Fill Patterns: Example Code

Download the source: TiledImages.java and ImageUtilities.java (plus WindowUtilities.java and ExitListener.java if you don't have them from the previous examples).

TiledImages.java

import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;

/** An example of using TexturePaint to fill objects with tiled
 *  images. Uses the getBufferedImage method of ImageUtilities to
 *  load an Image from a file and turn that into a BufferedImage.
 *
 *  From tutorial on learning Java2D at
 *  http://www.apl.jhu.edu/~hall/java/Java2D-Tutorial.html
 *
 *  1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class TiledImages extends JPanel {
  private String dir = System.getProperty("user.dir");
  private String imageFile1 = dir + "/images/marty.jpg";
  private TexturePaint imagePaint1;
  private Rectangle imageRect;
  private String imageFile2 = dir + "/images/bluedrop.gif";
  private TexturePaint imagePaint2;
  private int[] xPoints = { 30, 700, 400 };
  private int[] yPoints = { 30, 30, 600 };
  private Polygon imageTriangle = new Polygon(xPoints, yPoints, 3);

  public TiledImages() {
    BufferedImage image =
      ImageUtilities.getBufferedImage(imageFile1, this);
    imageRect =
      new Rectangle(235, 70, image.getWidth(), image.getHeight());
    imagePaint1 =
	  new TexturePaint(image, imageRect);
    image = ImageUtilities.getBufferedImage(imageFile2, this);
    imagePaint2 =
      new TexturePaint(image, new Rectangle(0, 0, 32, 32));
  }

  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D)g;
    g2d.setPaint(imagePaint2);
    g2d.fill(imageTriangle);
    g2d.setPaint(Color.blue);
    g2d.setStroke(new BasicStroke(5));
    g2d.draw(imageTriangle);
    g2d.setPaint(imagePaint1);
    g2d.fill(imageRect);
    g2d.setPaint(Color.black);
    g2d.draw(imageRect);
  }

  public static void main(String[] args) {
    WindowUtilities.openInJFrame(new TiledImages(), 750, 650);
  }
}

ImageUtilities.java

import java.awt.*;
import java.awt.image.*;

/** A class that simplifies a few common image operations, in
 *  particular creating a BufferedImage from an image file, and
 *  using MediaTracker to wait until an image or several images are
 *  done loading.
 *
 *  From tutorial on learning Java2D at
 *  http://www.apl.jhu.edu/~hall/java/Java2D-Tutorial.html
 *
 * 1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class ImageUtilities {

  /** Create Image from a file, then turn that into a BufferedImage.
   */

  public static BufferedImage getBufferedImage(String imageFile,
                                               Component c) {
    Image image = c.getToolkit().getImage(imageFile);
    waitForImage(image, c);
    BufferedImage bufferedImage =
      new BufferedImage(image.getWidth(c), image.getHeight(c),
                        BufferedImage.TYPE_INT_RGB);
    Graphics2D g2d = bufferedImage.createGraphics();
    g2d.drawImage(image, 0, 0, c);
    return(bufferedImage);
  }

  /** Take an Image associated with a file, and wait until it is
   *  done loading. Just a simple application of MediaTracker.
   *  If you are loading multiple images, don't use this
   *  consecutive times; instead use the version that takes
   *  an array of images.
   */

  public static boolean waitForImage(Image image, Component c) {
    MediaTracker tracker = new MediaTracker(c);
    tracker.addImage(image, 0);
    try {
      tracker.waitForAll();
    } catch(InterruptedException ie) {}
    return(!tracker.isErrorAny());
  }

  /** Take some Images associated with files, and wait until they
   *  are done loading. Just a simple application of MediaTracker.
   */

  public static boolean waitForImages(Image[] images, Component c) {
    MediaTracker tracker = new MediaTracker(c);
    for(int i=0; i<images.length; i++)
      tracker.addImage(images[i], 0);
    try {
      tracker.waitForAll();
    } catch(InterruptedException ie) {}
    return(!tracker.isErrorAny());
  }
}

3.8 Tiled Images as Fill Patterns: Example Output

Tutorial output: tiled image fill patterns in Java2D


4. Transparency in Java2D

4.1 Transparency: Overview

Java2D permits you to assign transparency (alpha) values to drawing operations so that the underlying graphics partially shows through when you draw shapes or images. You set transparency by creating an AlphaComposite object then passing it to the setComposite method of the Graphics2D object. You create an AlphaComposite by calling AlphaComposite.getInstance with a mixing rule designator and a transparency (or "alpha") value. There are 8 built-in mixing rules (see the AlphaComposite API for details), but the one normally used for drawing with transparency settings is AlphaComposite.SRC_OVER. Alpha values range from 0.0F (completely transparent) to 1.0F (completely opaque).

4.2 Transparency: Example Code

Download the source: TransparencyExample.java (plus WindowUtilities.java and ExitListener.java if you don't have them from the previous examples).
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;

/** An illustration of the use of AlphaComposite to make partially
 *  transparent drawings.
 *
 *  From tutorial on learning Java2D at
 *  http://www.apl.jhu.edu/~hall/java/Java2D-Tutorial.html
 *
 *  1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class TransparencyExample extends JPanel {
  private static int gap=10, width=60, offset=20,
                     deltaX=gap+width+offset;
  private Rectangle
    blueSquare = new Rectangle(gap+offset, gap+offset, width, width),
    redSquare = new Rectangle(gap, gap, width, width);

  private AlphaComposite makeComposite(float alpha) {
    int type = AlphaComposite.SRC_OVER;
    return(AlphaComposite.getInstance(type, alpha));
  }

  private void drawSquares(Graphics2D g2d, float alpha) {
    Composite originalComposite = g2d.getComposite();
    g2d.setPaint(Color.blue);
    g2d.fill(blueSquare);
    g2d.setComposite(makeComposite(alpha));
    g2d.setPaint(Color.red);
    g2d.fill(redSquare);
    g2d.setComposite(originalComposite);
  }

  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D)g;
    for(int i=0; i<11; i++) {
      drawSquares(g2d, i*0.1F);
      g2d.translate(deltaX, 0);
    }
  }

  public static void main(String[] args) {
    String title = "Transparency example: alpha of the top (red) " +
                   "square ranges from 0.0 at the left to 1.0 at " +
                   "the right. Bottom (blue) square is opaque.";
    WindowUtilities.openInJFrame(new TransparencyExample(),
                                 11*deltaX + 2*gap, deltaX + 3*gap,
                                 title, Color.lightGray);
  }
}

4.3 Transparency: Example Output

Tutorial output: transparent colors in Java2D


5. Using Local Fonts in Java2D

5.1 Local Fonts: Overview

You can use the same logical font names as in Java 1.1, namely Serif (e.g. Times), SansSerif (e.g. Helvetica or Arial), Monospaced (e.g. Courier), Dialog, and DialogInput.

You can also use arbitrary local fonts if you first look up the entire list, which may take a few seconds. Lookup the fonts via the getAvailableFontFamilyNames or getAllFonts methods of GraphicsEnvironment. E.g.:

  GraphicsEnvironment env = 
    GrapicsEnvironment.getLocalGraphicsEnvironment();
Then
  env.getAvailableFontFamilyNames(); 
or
  env.getAllFonts();  // Much slower!
Despite a misleading description in the API, trying to use an available local font in JDK 1.2 without first looking up the fonts as above gives the same result as asking for an unavailable font: a default font instead of the actual one. Note that getAllFonts returns an array of real Font objects that you can use like any other Font, but is much slower. If all you need to do is tell Java to make all local fonts available, always use getAvailableFontFamilyNames.

The best approach would be to loop down getAvailableFontFamilyNames, checking for your name, having several backup names to use if the first choice is not available. If you pass an unavailable family name to the Font constructor, a default font (SansSerif) will be used.

5.2 Example 1 -- Printing Out All Local Font Names

Download the source: ListFonts.java.
import java.awt.*;

/** Lists the names of all available fonts with Java2D in Java 1.2.
 *
 *  From tutorial on learning Java2D at
 *  http://www.apl.jhu.edu/~hall/java/Java2D-Tutorial.html
 *
 *  1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class ListFonts {
  public static void main(String[] args) {
    GraphicsEnvironment env =
      GraphicsEnvironment.getLocalGraphicsEnvironment();
    String[] fontNames = env.getAvailableFontFamilyNames();
    System.out.println("Available Fonts:");
    for(int i=0; i<fontNames.length; i++)
      System.out.println("  " + fontNames[i]);
  }
}

5.3 Example 2 -- Drawing with Local Fonts

Download the source: FontExample.java (plus GradientPaintExample.java, ShapeExample.java, WindowUtilities.java, and ExitListener.java if you don't have them from the previous examples).
import java.awt.*;

/** An example of using local fonts with Java2D in Java 1.2.
 *
 *  From tutorial on learning Java2D at
 *  http://www.apl.jhu.edu/~hall/java/Java2D-Tutorial.html
 *
 *  1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class FontExample extends GradientPaintExample {
  public FontExample() {
    GraphicsEnvironment env =
      GraphicsEnvironment.getLocalGraphicsEnvironment();
    env.getAvailableFontFamilyNames();
    setFont(new Font("Goudy Handtooled BT", Font.PLAIN, 100));
  }

  protected void drawBigString(Graphics2D g2d) {
    g2d.setPaint(Color.black);
    g2d.drawString("Java 2D", 25, 215);
  }

  public void paintComponent(Graphics g) {
    clear(g);
    Graphics2D g2d = (Graphics2D)g;
    drawGradientCircle(g2d);
    drawBigString(g2d);
  }

  public static void main(String[] args) {
    WindowUtilities.openInJFrame(new FontExample(), 380, 400);
  }
}

5.4 Drawing with Local Fonts: Example Output

Tutorial output: drawing with local fonts in Java2D


6. Stroke Styles in Java2D

6.1 Stroke Styles: Overview

In the AWT, the drawXxx methods of Graphics resulted in solid, 1-pixel wide lines. Furthermore, drawing commands that consisted of multiple line segments (e.g. drawRect and drawPolygon) had a predefined way of joining the line segments together and terminating segments that do not join to others. Java2D gives you much more flexibility. In addition to setting the pen color or pattern (via setPaint, as discussed in the previous section), Java2D permits you to set the pen thickness and dashing pattern, and to specify the way line segments end and are joined together. You do this by creating a BasicStroke object, then telling the Graphics2D object to use it via the setStroke method.

6.2 Stroke Attributes

Arguments to setStroke must implement the Stroke interface, and the BasicStroke class is the sole builtin class that implements Stroke. Here are the BasicStroke constructors:

6.3 Stroke Thickness: Example Code

Download the source: StrokeThicknessExample.java (plus FontExample.java, GradientPaintExample.java, ShapeExample.java, WindowUtilities.java, and ExitListener.java if you don't have them from the previous examples).
import java.awt.*;

/** An example of Stroke (pen) widths with Java2D in Java 1.2.
 *
 *  From tutorial on learning Java2D at
 *  http://www.apl.jhu.edu/~hall/java/Java2D-Tutorial.html
 *
 *  1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class StrokeThicknessExample extends FontExample {
  public void paintComponent(Graphics g) {
    clear(g);
    Graphics2D g2d = (Graphics2D)g;
    drawGradientCircle(g2d);
    drawBigString(g2d);
    drawThickCircleOutline(g2d);
  }

  protected void drawThickCircleOutline(Graphics2D g2d) {
    g2d.setPaint(Color.blue);
    g2d.setStroke(new BasicStroke(8)); // 8-pixel wide pen
    g2d.draw(getCircle());
  }

  public static void main(String[] args) {
    WindowUtilities.openInJFrame(new StrokeThicknessExample(),
                                 380, 400);
  }
}

6.4 Stroke Thickness: Example Output

Tutorial output: pen widths in Java2D

6.5 Dashed Lines: Example Code

Download the source: DashedStrokeExample.java (plus FontExample.java, GradientPaintExample.java, ShapeExample.java, WindowUtilities.java, and ExitListener.java if you don't have them from the previous examples).
import java.awt.*;

/** An example of dashed lines with Java2D in Java 1.2.
 *
 *  From tutorial on learning Java2D at
 *  http://www.apl.jhu.edu/~hall/java/Java2D-Tutorial.html
 *
 *  1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class DashedStrokeExample extends FontExample {
  public void paintComponent(Graphics g) {
    clear(g);
    Graphics2D g2d = (Graphics2D)g;
    drawGradientCircle(g2d);
    drawBigString(g2d);
    drawDashedCircleOutline(g2d);
  }

  protected void drawDashedCircleOutline(Graphics2D g2d) {
    g2d.setPaint(Color.blue);
    // 30 pixel line, 10 pixel gap, 10 pixel line, 10 pixel gap
    float[] dashPattern = { 30, 10, 10, 10 };
    g2d.setStroke(new BasicStroke(8, BasicStroke.CAP_BUTT,
                                  BasicStroke.JOIN_MITER, 10,
                                  dashPattern, 0));
    g2d.draw(getCircle());
  }

  public static void main(String[] args) {
    WindowUtilities.openInJFrame(new DashedStrokeExample(),
                                 380, 400);
  }
}

6.6 Dashed Lines: Example Output

Tutorial output: dashed lines in Java2D

6.7 Line Cap and Join Styles: Example Code

Download the source: LineStyles.java (plus WindowUtilities.java and ExitListener.java if you don't have them from the previous examples).
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;

/** An example of line cap and join styles with Java2D in Java 1.2.
 *
 *  From tutorial on learning Java2D at
 *  http://www.apl.jhu.edu/~hall/java/Java2D-Tutorial.html
 *
 *  1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class LineStyles extends JPanel {
  private GeneralPath path;
  private static int x = 30, deltaX = 150, y = 300, deltaY = 250,
                     thickness = 40;
  private Circle p1Large, p1Small, p2Large, p2Small, p3Large, p3Small;
  private int compositeType = AlphaComposite.SRC_OVER;
  private AlphaComposite transparentComposite =
    AlphaComposite.getInstance(compositeType, 0.4F);
  private int[] caps =
    { BasicStroke.CAP_SQUARE, BasicStroke.CAP_BUTT,
      BasicStroke.CAP_ROUND };
  private String[] capNames =
    { "CAP_SQUARE", "CAP_BUTT", "CAP_ROUND" };
  private int[] joins =
    { BasicStroke.JOIN_MITER, BasicStroke.JOIN_BEVEL,
      BasicStroke.JOIN_ROUND };
  private String[] joinNames =
    { "JOIN_MITER", "JOIN_BEVEL", "JOIN_ROUND" };

  public LineStyles() {
    path = new GeneralPath();
    path.moveTo(x, y);
    p1Large = new Circle(x, y, thickness/2);
    p1Small = new Circle(x, y, 2);
    path.lineTo(x + deltaX, y - deltaY);
    p2Large = new Circle(x + deltaX, y - deltaY, thickness/2);
    p2Small = new Circle(x + deltaX, y - deltaY, 2);
    path.lineTo(x + 2*deltaX, y);
    p3Large = new Circle(x + 2*deltaX, y, thickness/2);
    p3Small = new Circle(x + 2*deltaX, y, 2);
    setForeground(Color.blue);
    setFont(new Font("SansSerif", Font.BOLD, 20));
  }

  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D)g;
    g2d.setColor(Color.blue);
    for(int i=0; i>caps.length; i++) {
      BasicStroke stroke =
        new BasicStroke(thickness, caps[i], joins[i]);
      g2d.setStroke(stroke);
      g2d.draw(path);
      labelEndPoints(g2d, capNames[i], joinNames[i]);
      g2d.translate(3*x + 2*deltaX, 0);
    }
  }

  // Draw translucent circles to illustrate actual endpoints.
  // Include text labels to shold cap/join style.

  private void labelEndPoints(Graphics2D g2d,
                              String capLabel, String joinLabel) {
    Paint origPaint = g2d.getPaint();
    Composite origComposite = g2d.getComposite();
    g2d.setPaint(Color.red);
    g2d.setComposite(transparentComposite);
    g2d.fill(p1Large);
    g2d.fill(p2Large);
    g2d.fill(p3Large);
    g2d.setPaint(Color.yellow);
    g2d.setComposite(origComposite);
    g2d.fill(p1Small);
    g2d.fill(p2Small);
    g2d.fill(p3Small);
    g2d.setPaint(Color.black);
    g2d.drawString(capLabel, x + thickness - 5, y + 5);
    g2d.drawString(joinLabel, x + deltaX + thickness - 5, y - deltaY);
    g2d.setPaint(origPaint);
  }

  public static void main(String[] args) {
    WindowUtilities.openInJFrame(new LineStyles(),
                                 9*x + 6*deltaX, y + 60);
  }
}

class Circle extends Ellipse2D.Double {
  public Circle(double centerX, double centerY, double radius) {
    super(centerX - radius, centerY - radius, 2.0*radius, 2.0*radius);
  }
}

6.8 Line Cap and Join Styles: Example Output

Tutorial output: line cap and join styles in Java2D


7. Coordinate Transformations in Java2D

7.1 Coordinate Transformations: Overview

Java2D allows you to easily translate, rotate, scale, or shear the coordinate system. This is very convenient: it is often much easier to move the coordinate system than to calculate new coordinates for each of your points. Besides, for some data structures like ellipses and strings there is no other way to get rotated or stretched versions. The meanings of translate, rotate, and scale are clear: to move, to spin, or to stretch/shrink evenly in the x and/or y direction. Shear means to stretch unevenly: an x shear moves points to the right based on how far they are from the y axis; a y shear moves points down based on how far they are from the x axis.

The easiest way to picture what is happening is to imagine that the person doing the drawing has a picture frame that he lays down on top of a sheet of paper. The drawer always sits at the bottom of the frame. To apply a translation, you move the frame (moving the drawer with it), and do the drawing in the new location. You then move the frame back to its original location, and what you now see is the final result. Similarly, for a rotation, you spin the frame (and the drawer), draw, then spin back to see the result. Similarly for scaling and shears; modify the frame without touching the underlying sheet of paper, draw, then reverse the process to see the final result.

An outside observer watching this process would see the frame move in the direction specified by the transformation, but see the sheet of paper stay fixed. This is illustrated in the second column in the diagram below. The dotted rectangle represents the frame, while the gray rectangle represents the sheet of paper. On the other hand, to the person doing the drawing it would appear that the sheet of paper moved in the opposite way from that specified in the transformation, but that he didn't move at all. This is illustrated in the third column in the following diagram. The first column illustrates the starting configuration, and the fourth illustrates the final result. You can download the Java source code that generated this figure: TransformExample.java generates the individual illustrations (each cell in the table), and TransformTest.java put them all together into a JTable inside a JFrame.

Java2D tutorial: visualizing transformations

Figure 7-1: Visualizing Transformations

You can also perform more complex transformations (e.g. creating a mirror image by flipping around a line) by directly manipulating the underlying arrays that control the transformations. This is a bit more complicated to envision than the basic translatation, rotation, scaling, and shear transformations. The idea is that a new point (x2,y2) can be derived from an original point (x1,y1) as follows:

[ x2]   [  m00  m01  m02  ] [ x1 ]   [ m00x1 + m01y1 + m02 ]
[ y2] = [  m10  m11  m12  ] [ y1 ] = [ m10x1 + m11y1 + m12 ]
[ 1 ]   [   0    0    1  ] [ 1  ]   [         1         ]
Note that you can only supply six of the nine values in the transformation array (the mxx values). The bottom row is fixed at [ 0 0 1 ] to guarantee that the transformations preserve "straightness" and "parallelness" of lines. There are several ways of supplying this array to the AffineTransform constructor; see the AffineTransform API for details.

There are two basic ways to use transformations. You can create an AffineTransform object, set its parameters, and then assign that AffineTransform to the Graphics2D object via setTransform. This is your only choice if you want to do the more complex transformations permitted by setting explicit transformation matrices. Alternatively, for the basic transformations you can call translate, rotate, scale, and shear directly on the Graphics2D object.

7.2 Coordinate Translations and Rotations: Example Code

Download the source: RotationExample.java (plus StrokeThicknessExample.java, FontExample.java, GradientPaintExample.java, ShapeExample.java, WindowUtilities.java, and ExitListener.java if you don't have them from the previous examples).
import java.awt.*;

/** An example of coordinate translations and
 *  rotations with Java2D in Java 1.2.
 *
 *  From tutorial on learning Java2D at
 *  http://www.apl.jhu.edu/~hall/java/Java2D-Tutorial.html
 *
 *  1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class RotationExample extends StrokeThicknessExample {
  private Color[] colors = { Color.white, Color.black };

  public void paintComponent(Graphics g) {
    clear(g);
    Graphics2D g2d = (Graphics2D)g;
    drawGradientCircle(g2d);
    drawThickCircleOutline(g2d);
    // Move the origin to the center of the circle.
    g2d.translate(185.0, 185.0);
    for (int i=0; i<16; i++) {
      // Rotate the coordinate system around current
      // origin, which is at the center of the circle.
      g2d.rotate(Math.PI/8.0);
      g2d.setPaint(colors[i%2]);
      g2d.drawString("Java", 0, 0);
    }
  }

  public static void main(String[] args) {
    WindowUtilities.openInJFrame(new RotationExample(), 380, 400);
  }
}

7.3 Coordinate Translations and Rotations: Example Output

Tutorial output: coordinate transformations in Java2D

7.4 Shear Transformations

If you specify a non-zero x shear, then x values will be more and more shifted to the right the farther they are away from the y axis. For example, an x shear of 0.1 means that the x value will be shifted 10% of the distance the point is away from the y axis. Y shears are similar: points are shifted down in proportion to the distance they are away from the x axis.

7.5 Shear Transformations: Example Code

Download the source: ShearExample.java (plus WindowUtilities.java and ExitListener.java if you don't have them from the previous examples).
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;

/** An example of shear transformations with Java2D in Java 1.2.
 *
 *  From tutorial on learning Java2D at
 *  http://www.apl.jhu.edu/~hall/java/Java2D-Tutorial.html
 *
 *  1998 Marty Hall, http://www.apl.jhu.edu/~hall/java/
 */

public class ShearExample extends JPanel {
  private static int gap=10, width=100;
  private Rectangle rect = new Rectangle(gap, gap, 100, 100);

  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D)g;
    for (int i=0; i<5; i++) {
      g2d.setPaint(Color.red);
      g2d.fill(rect);
      // Each new square gets 0.2 more x shear
      g2d.shear(0.2, 0.0);
      g2d.translate(2*gap + width, 0);
    }
  }

  public static void main(String[] args) {
    String title =
      "Shear: x shear ranges from 0.0 for the leftmost 'square' " +
      "to 0.8 for the rightmost one.";
    WindowUtilities.openInJFrame(new ShearExample(),
                                20*gap + 5*width, 5*gap + width,
                                title);
  }
}

7.6 Shear Transformations: Example Output

Tutorial output: shear transformations in Java2D


8. Conclusions

8.1 Requesting More Accurate Drawing: Rendering Hints

Since Java2D already does a lot of calculations compared to the old AWT, there are several optional features that the designers chose to disable by default in order to improve performance. Turning them on results in crisper drawing, especially for rotated text. For example, the JTable on envisioning transformations resulted in excessively jagged text using the default settings. The most important two settings are to turn on antialiasing (smooth jagged lines by blending colors) and to simply request the highest-quality rendering. This approach is illustrated below:
RenderingHints renderHints =
  new RenderingHints(RenderingHints.KEY_ANTIALIASING,
                     RenderingHints.VALUE_ANTIALIAS_ON);
renderHints.put(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY);
...

public void paintComponent(Graphics g) {
  super.paintComponent(g);
  Graphics2D g2d = (Graphics2D)g;
  g2d.setRenderingHints(renderHints);
  ...
}

8.2 Summary of Using Java2D

8.3 Other Capabilities of Java2D

8.4 Learning More About Java2D


© 1998 Marty Hall. Java is a trademark of Sun Microsystems. The original of this document can be found at http://www.apl.jhu.edu/~hall/java/Java2D-Tutorial.html